﻿' Copyright © Microsoft Corporation.  All Rights Reserved.
' This code released under the terms of the 
' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
' <copyright file="TimeCode.vb" company="Microsoft">
'     Copyright © Microsoft Corporation. All rights reserved.
' </copyright>
' <summary>Implements the MediaPlayer class</summary>
' <author>Microsoft Expression Encoder Team</author>

Imports Microsoft.VisualBasic
Imports System
Imports System.Text.RegularExpressions
Imports System.Globalization
Imports System.Text

Namespace ExpressionMediaPlayer
	''' <summary>
	''' Represents a SMPTE 12M standard time code and provides conversion operations to various SMPTE time code formats and rates.
	''' </summary>
	''' <remarks>
	''' Framerates supported by the TimeCode class include, 23.98 IVTC Film Sync, 24fps Film Sync, 25fps PAL, 29.97 drop frame,
	''' 29.97 Non drop, and 30fps.
	''' </remarks>
	Public Partial Structure TimeCode
		Implements IComparable, IComparable(Of TimeCode), IEquatable(Of TimeCode)
		#Region "Private Fields"

		''' <summary>
		''' Regular expression string used for parsing out the timecode.
		''' </summary>
		Private Const smpteRegExString As String = "(?<Hours>\d{2}):(?<Minutes>\d{2}):(?<Seconds>\d{2})(?::|;)(?<Frames>\d{2})"

		''' <summary>
		''' Regular expression object used for validating timecode.
		''' </summary>
		Private Shared ReadOnly validateTimecode As New Regex(smpteRegExString, RegexOptions.CultureInvariant)

		''' <summary>
		''' The private Timespan used to track absolute time for this instance.
		''' </summary>
		Private ReadOnly absoluteTime As Double

		''' <summary>
		''' The frame rate for this instance.
        ''' </summary>
        Private _frameRate As SmpteFrameRate

#End Region

#Region "Constructors"

        ''' <summary>
        '''  Initializes a new instance of the TimeCode struct to a specified number of hours, minutes, and seconds.
        ''' </summary>
        ''' <param name="hours">Number of hours.</param>
        ''' <param name="minutes">Number of minutes.</param>
        ''' <param name="seconds">Number of seconds.</param>
        ''' <param name="frames">Number of frames.</param>
        ''' <param name="rate">The SMPTE frame rate.</param>
        ''' <exception cref="System.FormatException">
        ''' The parameters specify a TimeCode value less than TimeCode.MinValue.
        ''' or greater than TimeCode.MaxValue, or the values of time code components are not valid for the SMPTE framerate.
        ''' </exception>
        ''' <code source="..\Documentation\SdkDocSamples\TimecodeSamples.vb" region="CreateTimeCode_2398FromIntegers" lang="CSharp" title="Create TimeCode from Integers"/>
        Public Sub New(ByVal hours As Integer, ByVal minutes As Integer, ByVal seconds As Integer, ByVal frames As Integer, ByVal rate As SmpteFrameRate)
            Dim timeCode As String = String.Format(CultureInfo.InvariantCulture, "{0:D2}:{1:D2}:{2:D2}:{3:D2}", hours, minutes, seconds, frames)
            Me._frameRate = rate
            Me.absoluteTime = Smpte12mToAbsoluteTime(timeCode, Me._frameRate)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the TimeCode struct using an Int32 in hex format containing the time code value compatible with the Windows Media Format SDK.
        ''' Time code is stored so that the hexadecimal value is read as if it were a decimal value. That is, the time code value 0x01133512 does not represent decimal 18035986, rather it specifies 1 hour, 13 minutes, 35 seconds, and 12 frames.
        ''' </summary>
        ''' <param name="windowsMediaTimeCode">The integer value of the timecode.</param>
        ''' <param name="rate">The SMPTE frame rate.</param>
        Public Sub New(ByVal windowsMediaTimeCode As Integer, ByVal rate As SmpteFrameRate)
            ' Timecode is provided back formatted as hexadecimal bytes read in single bytes from left to right.
            Dim timeCodeBytes() As Byte = BitConverter.GetBytes(windowsMediaTimeCode)
            Dim timeCode As String = String.Format(CultureInfo.InvariantCulture, "{3:x2}:{2:x2}:{1:x2}:{0:x2}", timeCodeBytes(0), timeCodeBytes(1), timeCodeBytes(2), timeCodeBytes(3))

            Me._frameRate = rate
            Me.absoluteTime = Smpte12mToAbsoluteTime(timeCode, Me._frameRate)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the TimeCode struct using a time code string that contains the framerate at the end of the string.
        ''' </summary>
        ''' <remarks>
        ''' Pass in a timecode in the format "timecode@framrate". 
        ''' Supported rates include @23.98, @24, @25, @29.97, @30
        ''' </remarks>
        ''' <example>
        ''' "00:01:00:00@29.97" is equivalent to 29.97 non drop frame.
        ''' "00:01:00;00@29.97" is equivalent to 29.97 drop frame.
        ''' </example>
        ''' <param name="timeCodeAndRate">The SMPTE 12m time code string.</param>
        Public Sub New(ByVal timeCodeAndRate As String)
            Dim timeAndRate() As String = timeCodeAndRate.Split("@"c)

            Dim time As String = String.Empty
            Dim rate As String = String.Empty

            If timeAndRate.Length = 1 Then
                time = timeAndRate(0)
                rate = "29.97"
            ElseIf timeAndRate.Length = 2 Then
                time = timeAndRate(0)
                rate = timeAndRate(1)
            End If

            Me._frameRate = SmpteFrameRate.Smpte2997NonDrop

            If rate = "29.97" AndAlso time.IndexOf(";"c) > -1 Then
                Me._frameRate = SmpteFrameRate.Smpte2997Drop
            ElseIf rate = "29.97" AndAlso time.IndexOf(";"c) = -1 Then
                Me._frameRate = SmpteFrameRate.Smpte2997NonDrop
            ElseIf rate = "25" Then
                Me._frameRate = SmpteFrameRate.Smpte25
            ElseIf rate = "23.98" Then
                Me._frameRate = SmpteFrameRate.Smpte2398
            ElseIf rate = "24" Then
                Me._frameRate = SmpteFrameRate.Smpte24
            ElseIf rate = "30" Then
                Me._frameRate = SmpteFrameRate.Smpte30
            End If

            Me.absoluteTime = Smpte12mToAbsoluteTime(time, Me._frameRate)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the TimeCode struct using a time code string and a SMPTE framerate.
        ''' </summary>
        ''' <param name="timeCode">The SMPTE 12m time code string.</param>
        ''' <param name="rate">The SMPTE framerate used for this instance of TimeCode.</param>
        Public Sub New(ByVal timeCode As String, ByVal rate As SmpteFrameRate)
            Me._frameRate = rate
            Me.absoluteTime = Smpte12mToAbsoluteTime(timeCode, Me._frameRate)
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the TimeCode struct using an absolute time value, and the SMPTE framerate.
        ''' </summary>
        ''' <param name="absoluteTime">The double that represents the absolute time value.</param>
        ''' <param name="rate">The SMPTE framerate that this instance should use.</param>
        Public Sub New(ByVal absoluteTime As Double, ByVal rate As SmpteFrameRate)
            Me.absoluteTime = absoluteTime
            Me._frameRate = rate
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the TimeCode struct a long value that represents a value of a 27 Mhz clock.
        ''' </summary>
        ''' <param name="ticks27Mhz">The long value in 27 Mhz clock ticks.</param>
        ''' <param name="rate">The SMPTE frame rate to use for this instance.</param>
        Public Sub New(ByVal ticks27Mhz As Long, ByVal rate As SmpteFrameRate)
            Me.absoluteTime = Ticks27MhzToAbsoluteTime(ticks27Mhz)
            Me._frameRate = rate
        End Sub

#End Region

#Region "Public Static Properties"

        ''' <summary>
        '''  Gets the number of ticks in 1 day. 
        '''  This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerDay() As Long
            Get
                Return 864000000000
            End Get
        End Property

        ''' <summary>
        '''  Gets the number of absolute time ticks in 1 day. 
        '''  This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerDayAbsoluteTime() As Double
            Get
                Return 86400
            End Get
        End Property

        ''' <summary>
        '''  Gets the number of ticks in 1 hour. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerHour() As Long
            Get
                Return 36000000000
            End Get
        End Property

        ''' <summary>
        '''  Gets the number of absolute time ticks in 1 hour. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerHourAbsoluteTime() As Double
            Get
                Return 3600
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of ticks in 1 millisecond. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerMillisecond() As Long
            Get
                Return 10000
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of ticks in 1 millisecond. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerMillisecondAbsoluteTime() As Double
            Get
                Return 0.001R
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of ticks in 1 minute. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerMinute() As Long
            Get
                Return 600000000
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of absolute time ticks in 1 minute. This field is constant.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerMinuteAbsoluteTime() As Double
            Get
                Return 60
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of ticks in 1 second.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerSecond() As Long
            Get
                Return 10000000
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of ticks in 1 second.
        ''' </summary>
        Public Shared ReadOnly Property TicksPerSecondAbsoluteTime() As Double
            Get
                Return 1.0R
            End Get
        End Property

        ''' <summary>
        '''  Gets the maximum TimeCode value. The Max value for Timecode. This field is read-only.
        ''' </summary>
        Public Shared ReadOnly Property MaxValue() As Double
            Get
                Return 86399
            End Get
        End Property

        ''' <summary>
        ''' Gets the minimum TimeCode value. This field is read-only.
        ''' </summary>
        Public Shared ReadOnly Property MinValue() As Double
            Get
                Return 0
            End Get
        End Property

#End Region

#Region "Public Properties"

        ''' <summary>
        ''' Gets the absolute time in seconds of the current TimeCode object.
        ''' </summary>
        ''' <returns>
        '''  A double that is the absolute time in seconds duration of the current TimeCode object.
        ''' </returns>
        Public ReadOnly Property Duration() As Double
            Get
                Return Me.absoluteTime
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the current SMPTE framerate for this TimeCode instance.
        ''' </summary>
        Public Property FrameRate() As SmpteFrameRate
            Get
                Return Me._frameRate
            End Get
            Set(ByVal value As SmpteFrameRate)
                Me._frameRate = value
            End Set
        End Property

        ''' <summary>
        '''  Gets the number of whole hours represented by the current TimeCode
        '''  structure.
        ''' </summary>
        ''' <returns>
        '''  The hour component of the current TimeCode structure. The return value
        '''     ranges from 0 through 23.
        ''' </returns>
        Public ReadOnly Property HoursSegment() As Integer
            Get
                Dim timeCode As String = AbsoluteTimeToSmpte12M(Me.absoluteTime, Me._frameRate)
                Dim hours As String = timeCode.Substring(0, 2)
                Return Int32.Parse(hours, CultureInfo.InvariantCulture)
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of whole minutes represented by the current TimeCode structure.
        ''' </summary>
        ''' <returns>
        ''' The minute component of the current TimeCode structure. The return
        ''' value ranges from 0 through 59.
        ''' </returns>
        Public ReadOnly Property MinutesSegment() As Integer
            Get
                Dim timeCode As String = AbsoluteTimeToSmpte12M(Me.absoluteTime, Me._frameRate)
                Dim minutes As String = timeCode.Substring(3, 2)
                Return Int32.Parse(minutes, CultureInfo.InvariantCulture)
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of whole seconds represented by the current TimeCode structure.
        ''' </summary>
        ''' <returns>
        '''  The second component of the current TimeCode structure. The return
        '''    value ranges from 0 through 59.
        ''' </returns>
        Public ReadOnly Property SecondsSegment() As Integer
            Get
                Dim timeCode As String = AbsoluteTimeToSmpte12M(Me.absoluteTime, Me._frameRate)
                Dim seconds As String = timeCode.Substring(6, 2)
                Return Int32.Parse(seconds, CultureInfo.InvariantCulture)
            End Get
        End Property

        ''' <summary>
        ''' Gets the number of whole frames represented by the current TimeCode
        '''     structure.
        ''' </summary>
        ''' <returns>
        ''' The frame component of the current TimeCode structure. The return
        '''     value depends on the framerate selected for this instance. All frame counts start at zero.
        ''' </returns>
        Public ReadOnly Property FramesSegment() As Integer
            Get
                Dim timeCode As String = AbsoluteTimeToSmpte12M(Me.absoluteTime, Me._frameRate)
                Dim frames As String = timeCode.Substring(9, 2)
                Return Int32.Parse(frames, CultureInfo.InvariantCulture)
            End Get
        End Property

        ''' <summary>
        ''' Gets the value of the current TimeCode structure expressed in whole
        '''     and fractional hours.
        ''' </summary>
        ''' <returns>
        '''  The total number of hours represented by this instance.
        ''' </returns>
        Public ReadOnly Property TotalHours() As Double
            Get
                Dim framecount As Long = AbsoluteTimeToFrames(Me.absoluteTime, Me._frameRate)
                Return (framecount / 108000.0R) Mod 24
            End Get
        End Property

        ''' <summary>
        ''' Gets the value of the current TimeCode structure expressed in whole
        ''' and fractional minutes.
        ''' </summary>
        ''' <returns>
        '''  The total number of minutes represented by this instance.
        ''' </returns>
        Public ReadOnly Property TotalMinutes() As Double
            Get
                Dim framecount As Long = AbsoluteTimeToFrames(Me.absoluteTime, Me._frameRate)

                Dim minutes As Double

                Select Case Me._frameRate
                    Case SmpteFrameRate.Smpte2398, SmpteFrameRate.Smpte24
                        minutes = framecount / 1400.0R
                    Case SmpteFrameRate.Smpte25
                        minutes = framecount / 1500.0R
                    Case SmpteFrameRate.Smpte2997Drop, SmpteFrameRate.Smpte2997NonDrop, SmpteFrameRate.Smpte30
                        minutes = framecount / 1800.0R
                    Case Else
                        minutes = framecount / 1800.0R
                End Select

                Return minutes
            End Get
        End Property

        ''' <summary>
        ''' Gets the value of the current TimeCode structure expressed in whole
        ''' and fractional seconds.
        ''' </summary>
        ''' <returns>
        ''' The total number of seconds represented by this instance.
        ''' </returns>
        Public ReadOnly Property TotalSeconds() As Double
            Get
                Return Me.absoluteTime
            End Get
        End Property

        ''' <summary>
        ''' Gets the value of the current TimeCode structure expressed in frames.
        ''' </summary>
        ''' <returns>
        '''  The total number of frames represented by this instance.
        ''' </returns>
        Public ReadOnly Property TotalFrames() As Long
            Get
                Return AbsoluteTimeToFrames(Me.absoluteTime, Me._frameRate)
            End Get
        End Property

#End Region

#Region "Operator Overloads"

        ''' <summary>
        ''' Subtracts a specified TimeCode from another specified TimeCode.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>A TimeCode whose value is the result of the value of time1 minus the value of time2.
        ''' </returns>
        ''' <exception cref="System.OverflowException">The return value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.
        ''' </exception>
        Public Shared Operator -(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As TimeCode
            Dim time As Double = time1.absoluteTime - time2.absoluteTime

            If time < MinValue Then
                Throw New OverflowException(Resources.Smpte12MOverflowException)
            End If

            Return New TimeCode(time, time1.FrameRate)
        End Operator

        ''' <summary>
        ''' Indicates whether two TimeCode instances are not equal.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>true if the values of time1 and time2 are not equal; otherwise, false.</returns>
        Public Shared Operator <>(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If Math.Floor(Math.Pow(10, 2) * time1.absoluteTime) <> Math.Floor(Math.Pow(10, 2) * time2.absoluteTime) Then
                Return True
            End If

            Return False
        End Operator

        ''' <summary>
        ''' Adds two specified TimeCode instances.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>A TimeCode whose value is the sum of the values of time1 and time2.</returns>
        ''' <exception cref="System.OverflowException">
        ''' The resulting TimeCode is less than TimeCode.MinValue or greater than TimeCode.MaxValue.
        ''' </exception>
        Public Shared Operator +(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As TimeCode
            Dim time3 As New TimeCode(time1.absoluteTime + time2.absoluteTime, time1.FrameRate)
            If time3.absoluteTime >= MaxValue Then
                Throw New OverflowException(Resources.Smpte12MOverflowException)
            End If

            Return time3
        End Operator

        ''' <summary>
        '''  Indicates whether a specified TimeCode is less than another
        '''  specified TimeCode.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns> True if the value of time1 is less than the value of time2; otherwise, false.</returns>
        Public Shared Operator <(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If time1.absoluteTime < time2.absoluteTime Then
                Return True
            End If

            Return False
        End Operator

        ''' <summary>
        '''  Indicates whether a specified TimeCode is less than or equal to another
        '''  specified TimeCode.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>True if the value of time1 is less than or equal to the value of time2; otherwise, false.</returns>
        Public Shared Operator <=(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If (time1.absoluteTime < time2.absoluteTime) OrElse (time1 = time2) Then
                Return True
            End If

            Return False
        End Operator

        ''' <summary>
        '''  Indicates whether two TimeCode instances are equal.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>true if the values of time1 and time2 are equal; otherwise, false.</returns>
        Public Shared Operator =(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            ' TODO:  Check that this works for all framerates
            If Math.Floor(Math.Pow(10, 2) * time1.absoluteTime) = Math.Floor(Math.Pow(10, 2) * time2.absoluteTime) Then
                Return True
            End If

            Return False
        End Operator

        ''' <summary>
        ''' Indicates whether a specified TimeCode is greater than another specified
        '''     TimeCode.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>true if the value of time1 is greater than the value of time2; otherwise, false.
        ''' </returns>
        Public Shared Operator >(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If time1.absoluteTime > time2.absoluteTime Then
                Return True
            End If

            Return False
        End Operator

        ''' <summary>
        ''' Indicates whether a specified TimeCode is greater than or equal to
        '''     another specified TimeCode.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>True if the value of time1 is greater than or equal to the value of time2; otherwise, false.</returns>
        Public Shared Operator >=(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If (time1.absoluteTime > time2.absoluteTime) OrElse (time1 = time2) Then
                Return True
            End If

            Return False
        End Operator

#End Region

#Region "Public Static Methods"

        ''' <summary>
        ''' Returns a SMPTE 12M formatted time code string from a 27Mhz ticks value.
        ''' </summary>
        ''' <param name="ticks27Mhz">27Mhz ticks value.</param>
        ''' <param name="rate">The SMPTE time code framerated desired.</param>
        ''' <returns>A SMPTE 12M formatted time code string.</returns>
        Public Shared Function Ticks27MhzToSmpte12M(ByVal ticks27Mhz As Long, ByVal rate As SmpteFrameRate) As String
            Select Case rate
                Case SmpteFrameRate.Smpte2398
                    Return Ticks27MhzToSmpte12M_23_98fps(ticks27Mhz)
                Case SmpteFrameRate.Smpte24
                    Return Ticks27MhzToSmpte12M_24fps(ticks27Mhz)
                Case SmpteFrameRate.Smpte25
                    Return Ticks27MhzToSmpte12M_25fps(ticks27Mhz)
                Case SmpteFrameRate.Smpte2997Drop
                    Return Ticks27MhzToSmpte12M_29_27_Drop(ticks27Mhz)
                Case SmpteFrameRate.Smpte2997NonDrop
                    Return Ticks27MhzToSmpte12M_29_27_NonDrop(ticks27Mhz)
                Case SmpteFrameRate.Smpte30
                    Return Ticks27MhzToSmpte12M_30fps(ticks27Mhz)
                Case Else
                    Return Ticks27MhzToSmpte12M_30fps(ticks27Mhz)
            End Select
        End Function

        ''' <summary>
        ''' Compares two TimeCode values and returns an integer that indicates their relationship.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>
        ''' Value Condition -1 time1 is less than time2, 0 time1 is equal to time2, 1 time1 is greater than time2.
        ''' </returns>
        Public Shared Function Compare(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Integer
            If time1 < time2 Then
                Return -1
            End If

            If time1 = time2 Then
                Return 0
            End If

            Return 1
        End Function

        ''' <summary>
        '''  Returns a value indicating whether two specified instances of TimeCode
        '''  are equal.
        ''' </summary>
        ''' <param name="time1">The first TimeCode.</param>
        ''' <param name="time2">The second TimeCode.</param>
        ''' <returns>true if the values of time1 and time2 are equal; otherwise, false.</returns>
        Public Overloads Shared Function Equals(ByVal time1 As TimeCode, ByVal time2 As TimeCode) As Boolean
            If time1 = time2 Then
                Return True
            End If

            Return False
        End Function

        ''' <summary>
        '''  Returns a TimeCode that represents a specified number of hours, where
        '''  the specification is accurate to the nearest millisecond.
        ''' </summary>
        ''' <param name="hours">A number of hours accurate to the nearest millisecond.</param>
        ''' <param name="rate">The desired framerate for this instance.</param>
        ''' <returns> A TimeCode that represents value.</returns>
        ''' <exception cref="System.OverflowException">
        ''' value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.
        ''' -or-value is System.Double.PositiveInfinity.-or-value is System.Double.NegativeInfinity.
        ''' </exception>
        ''' <exception cref="System.FormatException">
        ''' value is equal to System.Double.NaN.
        ''' </exception>
        Public Shared Function FromHours(ByVal hours As Double, ByVal rate As SmpteFrameRate) As TimeCode
            Dim absoluteTime As Double = hours * TicksPerHourAbsoluteTime
            Return New TimeCode(absoluteTime, rate)
        End Function

        ''' <summary>
        '''   Returns a TimeCode that represents a specified number of minutes,
        '''   where the specification is accurate to the nearest millisecond.
        ''' </summary>
        ''' <param name="minutes">A number of minutes, accurate to the nearest millisecond.</param>
        ''' <param name="rate">The <see cref="SmpteFrameRate"/> to use for the calculation.</param>
        ''' <returns>A TimeCode that represents value.</returns>
        ''' <exception cref="System.OverflowException">
        ''' value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.-or-value
        ''' is System.Double.PositiveInfinity.-or-value is System.Double.NegativeInfinity.
        ''' </exception>
        ''' <exception cref="System.ArgumentException">
        ''' value is equal to System.Double.NaN.
        ''' </exception>
        Public Shared Function FromMinutes(ByVal minutes As Double, ByVal rate As SmpteFrameRate) As TimeCode
            Dim absoluteTime As Double = minutes * TicksPerMinuteAbsoluteTime
            Return New TimeCode(absoluteTime, rate)
        End Function

        ''' <summary>
        ''' Returns a TimeCode that represents a specified number of seconds,
        ''' where the specification is accurate to the nearest millisecond.
        ''' </summary>
        ''' <param name="seconds">A number of seconds, accurate to the nearest millisecond.</param>
        ''' /// <param name="rate">The framerate of the Timecode.</param>
        ''' <returns>A TimeCode that represents value.</returns>
        ''' <exception cref="System.OverflowException">
        ''' value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.-or-value
        '''  is System.Double.PositiveInfinity.-or-value is System.Double.NegativeInfinity.
        ''' </exception>
        ''' <exception cref="System.ArgumentException">
        ''' value is equal to System.Double.NaN.
        ''' </exception>
        Public Shared Function FromSeconds(ByVal seconds As Double, ByVal rate As SmpteFrameRate) As TimeCode
            Return New TimeCode(seconds, rate)
        End Function

        ''' <summary>
        ''' Returns a TimeCode that represents a specified number of frames.
        ''' </summary>
        ''' <param name="frames">A number of frames.</param>
        ''' <param name="rate">The framerate of the Timecode.</param>
        ''' <returns>A TimeCode that represents value.</returns>
        ''' <exception cref="System.OverflowException">
        '''  value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.-or-value
        '''    is System.Double.PositiveInfinity.-or-value is System.Double.NegativeInfinity.
        ''' </exception>
        ''' <exception cref="System.ArgumentException">
        ''' value is equal to System.Double.NaN.
        ''' </exception>
        Public Shared Function FromFrames(ByVal frames As Long, ByVal rate As SmpteFrameRate) As TimeCode
            Dim abs As Double = FramesToAbsoluteTime(frames, rate)
            Return New TimeCode(abs, rate)
        End Function

        ''' <summary>
        ''' Returns a TimeCode that represents a specified time, where the specification
        '''  is in units of ticks.
        ''' </summary>
        ''' <param name="ticks"> A number of ticks that represent a time.</param>
        ''' <param name="rate">The Smpte framerate.</param>
        ''' <returns>A TimeCode with a value of value.</returns>
        Public Shared Function FromTicks(ByVal ticks As Long, ByVal rate As SmpteFrameRate) As TimeCode
            Dim absoluteTime As Double = Math.Pow(10, -7) * ticks
            Return New TimeCode(absoluteTime, rate)
        End Function

        ''' <summary>
        ''' Returns a TimeCode that represents a specified time, where the specification is 
        ''' in units of 27 Mhz clock ticks.
        ''' </summary>
        ''' <param name="ticks27Mhz">A number of ticks in 27 Mhz clock format.</param>
        ''' <param name="rate">A Smpte framerate.</param>
        ''' <returns>A TimeCode.</returns>
        Public Shared Function FromTicks27Mhz(ByVal ticks27Mhz As Long, ByVal rate As SmpteFrameRate) As TimeCode
            Dim absoluteTime As Double = Ticks27MhzToAbsoluteTime(ticks27Mhz)

            Return New TimeCode(absoluteTime, rate)
        End Function

        ''' <summary>
        ''' Returns a TimeCode that represents a specified time, where the specification is 
        ''' in units of absolute time.
        ''' </summary>
        ''' <param name="time">The absolute time in 100 nanosecond units.</param>
        ''' <param name="rate">The SMPTE framerate.</param>
        ''' <returns>A TimeCode.</returns>
        Public Shared Function FromAbsoluteTime(ByVal time As Double, ByVal rate As SmpteFrameRate) As TimeCode
            Return New TimeCode(time, rate)
        End Function

        ''' <summary>
        ''' Validates that the string provided is in the correct format for SMPTE 12M time code.
        ''' </summary>
        ''' <param name="timeCode">String that is the time code.</param>
        ''' <returns>True if this is a valid SMPTE 12M time code string.</returns>
        Public Shared Function ValidateSmpte12MTimeCode(ByVal timeCode As String) As Boolean
            Dim times() As String = timeCode.Split(":"c, ";"c)

            Dim hours As Integer = Int32.Parse(times(0), CultureInfo.InvariantCulture)
            Dim minutes As Integer = Int32.Parse(times(1), CultureInfo.InvariantCulture)
            Dim seconds As Integer = Int32.Parse(times(2), CultureInfo.InvariantCulture)
            Dim frames As Integer = Int32.Parse(times(3), CultureInfo.InvariantCulture)

            If (hours >= 24) OrElse (minutes >= 60) OrElse (seconds >= 60) OrElse (frames >= 30) Then
                Return False
            End If

            Return True
        End Function

        ''' <summary>
        ''' Validates that the hexadecimal formatted integer provided is in the correct format for SMPTE 12M time code
        ''' Time code is stored so that the hexadecimal value is read as if it were an integer value. 
        ''' That is, the time code value 0x01133512 does not represent integer 18035986, rather it specifies 1 hour, 13 minutes, 35 seconds, and 12 frames.      
        ''' </summary>
        ''' <param name="windowsMediaTimeCode">Integer that is the time code stored in hexadecimal format.</param>
        ''' <returns>True if this is a valid SMPTE 12M time code string.</returns>
        Public Shared Function ValidateSmpte12MTimeCode(ByVal windowsMediaTimeCode As Integer) As Boolean
            Dim timeCodeBytes() As Byte = BitConverter.GetBytes(windowsMediaTimeCode)
            Dim timeCode As String = String.Format(CultureInfo.InvariantCulture, "{3:x2}:{2:x2}:{1:x2}:{0:x2}", timeCodeBytes(0), timeCodeBytes(1), timeCodeBytes(2), timeCodeBytes(3))
            Dim times() As String = timeCode.Split(":"c, ";"c)

            Dim hours As Integer = Int32.Parse(times(0), CultureInfo.InvariantCulture)
            Dim minutes As Integer = Int32.Parse(times(1), CultureInfo.InvariantCulture)
            Dim seconds As Integer = Int32.Parse(times(2), CultureInfo.InvariantCulture)
            Dim frames As Integer = Int32.Parse(times(3), CultureInfo.InvariantCulture)

            If (hours >= 24) OrElse (minutes >= 60) OrElse (seconds >= 60) OrElse (frames >= 30) Then
                Return False
            End If

            Return True
        End Function

        ''' <summary>
        ''' Returns the value of the provided time code string and framerate in 27Mhz ticks.
        ''' </summary>
        ''' <param name="timeCode">The SMPTE 12M formatted time code string.</param>
        ''' <param name="rate">The SMPTE framerate.</param>
        ''' <returns>A long that represents the value of the time code in 27Mhz ticks.</returns>
        Public Shared Function Smpte12MToTicks27Mhz(ByVal timeCode As String, ByVal rate As SmpteFrameRate) As Long
            Select Case rate
                Case SmpteFrameRate.Smpte2398
                    Return Smpte12M_23_98fpsToTicks27Mhz(timeCode)
                Case SmpteFrameRate.Smpte24
                    Return Smpte12M_24fpsToTicks27Mhz(timeCode)
                Case SmpteFrameRate.Smpte25
                    Return Smpte12M_25fpsToTicks27Mhz(timeCode)
                Case SmpteFrameRate.Smpte2997Drop
                    Return Smpte12M_29_27_DropToTicks27Mhz(timeCode)
                Case SmpteFrameRate.Smpte2997NonDrop
                    Return Smpte12M_29_27_NonDropToTicks27Mhz(timeCode)
                Case SmpteFrameRate.Smpte30
                    Return Smpte12M_30fpsToTicks27Mhz(timeCode)
                Case Else
                    Return Smpte12M_30fpsToTicks27Mhz(timeCode)
            End Select
        End Function

        ''' <summary>
        ''' Parses a framerate value as double and converts it to a member of the SmpteFrameRate enumeration.
        ''' </summary>
        ''' <param name="rate">Double value of the framerate.</param>
        ''' <returns>A SmpteFrameRate enumeration value that matches the incoming rates.</returns>
        Public Shared Function ParseFrameRate(ByVal rate As Double) As SmpteFrameRate
            Dim rateRounded As Integer = CInt(Fix(Math.Floor(rate)))

            Select Case rateRounded
                Case 23
                    Return SmpteFrameRate.Smpte2398
                Case 24
                    Return SmpteFrameRate.Smpte24
                Case 25
                    Return SmpteFrameRate.Smpte25
                Case 29
                    Return SmpteFrameRate.Smpte2997NonDrop
                Case 30
                    Return SmpteFrameRate.Smpte30
                Case 50
                    Return SmpteFrameRate.Smpte25
                Case 60
                    Return SmpteFrameRate.Smpte30
                Case 59
                    Return SmpteFrameRate.Smpte2997NonDrop
            End Select

            Return SmpteFrameRate.Unknown
        End Function

        ''' <summary>
        ''' Converts an absolute time and a frame rate to a formatted string.
        ''' </summary>
        ''' <param name="absoluteTime">Double precision floating point time in seconds.</param>
        ''' <param name="frameRate">SMPTE frame rate enum.</param>
        ''' <returns>A string that contains the correct format.</returns>
        Public Shared Function ConvertToString(ByVal absoluteTime As Double, ByVal frameRate As SmpteFrameRate) As String
            Dim timeCode As New TimeCode(absoluteTime, frameRate)
            Return timeCode.ToString()
        End Function

#End Region

#Region "Public Methods"

        ''' <summary>
        ''' Adds the specified TimeCode to this instance.
        ''' </summary>
        ''' <param name="ts">A TimeCode.</param>
        ''' <returns>A TimeCode that represents the value of this instance plus the value of ts.
        ''' </returns>
        ''' <exception cref="System.OverflowException">
        ''' The resulting TimeCode is less than TimeCode.MinValue or greater than TimeCode.MaxValue.
        ''' </exception>
        Public Function Add(ByVal ts As TimeCode) As TimeCode
            Return Me + ts
        End Function

        ''' <summary>
        '''  Compares this instance to a specified object and returns an indication of
        '''   their relative values.
        ''' </summary>
        ''' <param name="value">An object to compare, or null.</param>
        ''' <returns>
        '''  Value Condition -1 The value of this instance is less than the value of value.
        '''    0 The value of this instance is equal to the value of value. 1 The value
        '''    of this instance is greater than the value of value.-or- value is null.
        ''' </returns>
        ''' <exception cref="System.ArgumentException">
        '''  value is not a TimeCode.
        ''' </exception>
        Public Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
            If Not (TypeOf obj Is TimeCode) Then
                Throw New ArgumentException(Resources.Smpte12MOutOfRange)
            End If

            Dim time1 As TimeCode = CType(obj, TimeCode)

            If Me < time1 Then
                Return -1
            End If

            If Me = time1 Then
                Return 0
            End If

            Return 1
        End Function

        ''' <summary>
        ''' Compares this instance to a specified TimeCode object and returns
        ''' an indication of their relative values.
        ''' </summary>
        ''' <param name="other"> A TimeCode object to compare to this instance.</param>
        ''' <returns>
        ''' A signed number indicating the relative values of this instance and value.Value
        ''' Description A negative integer This instance is less than value. Zero This
        ''' instance is equal to value. A positive integer This instance is greater than
        ''' value.
        ''' </returns>
        Public Function CompareTo(ByVal other As TimeCode) As Integer Implements IComparable(Of TimeCode).CompareTo
            If Me < other Then
                Return -1
            End If

            If Me = other Then
                Return 0
            End If

            Return 1
        End Function

        ''' <summary>
        '''  Returns a value indicating whether this instance is equal to a specified
        '''  object.
        ''' </summary>
        ''' <param name="other">An object to compare with this instance.</param>
        ''' <returns>
        ''' True if value is a TimeCode object that represents the same time interval
        ''' as the current TimeCode structure; otherwise, false.
        ''' </returns>
        Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
            If Me = CType(obj, TimeCode) Then
                Return True
            End If

            Return False
        End Function

        ''' <summary>
        ''' Returns a value indicating whether this instance is equal to a specified
        '''     TimeCode object.
        ''' </summary>
        ''' <param name="obj">An TimeCode object to compare with this instance.</param>
        ''' <returns>true if obj represents the same time interval as this instance; otherwise, false.
        ''' </returns>
        Public Overloads Function Equals(ByVal other As TimeCode) As Boolean Implements IEquatable(Of TimeCode).Equals
            If Me = other Then
                Return True
            End If

            Return False
        End Function

        ''' <summary>
        ''' Returns a hash code for this instance.
        ''' </summary>
        ''' <returns> A 32-bit signed integer hash code.</returns>
        Public Overrides Function GetHashCode() As Integer
            Return Me.GetHashCode()
        End Function

        ''' <summary>
        ''' Subtracts the specified TimeCode from this instance.
        ''' </summary>
        ''' <param name="ts">A TimeCode.</param>
        ''' <returns>A TimeCode whose value is the result of the value of this instance minus the value of ts.</returns>
        ''' <exception cref="OverflowException">The return value is less than TimeCode.MinValue or greater than TimeCode.MaxValue.</exception>
        Public Function Subtract(ByVal ts As TimeCode) As TimeCode
            Return Me - ts
        End Function

        ''' <summary>
        ''' Returns the SMPTE 12M string representation of the value of this instance.
        ''' </summary>
        ''' <returns>
        ''' A string that represents the value of this instance. The return value is
        '''     of the form: hh:mm:ss:ff for non-drop frame and hh:mm:ss;ff for drop frame code
        '''     with "hh" hours, ranging from 0 to 23, "mm" minutes
        '''     ranging from 0 to 59, "ss" seconds ranging from 0 to 59, and  "ff"  based on the 
        '''     chosen framerate to be used by the time code instance.
        ''' </returns>
        Public Overrides Function ToString() As String
            Return AbsoluteTimeToSmpte12M(Me.absoluteTime, Me._frameRate)
        End Function

        ''' <summary>
        ''' Outputs a string of the current time code in the requested framerate.
        ''' </summary>
        ''' <param name="rate">The SmpteFrameRate required for the string output.</param>
        ''' <returns>SMPTE 12M formatted time code string converted to the requested framerate.</returns>
        Public Overloads Function ToString(ByVal rate As SmpteFrameRate) As String
            Return AbsoluteTimeToSmpte12M(Me.absoluteTime, rate)
        End Function

        ''' <summary>
        ''' Returns the value of this instance in 27 Mhz ticks.
        ''' </summary>
        ''' <returns>A long value that is in 27 Mhz ticks.</returns>
        Public Function ToTicks27Mhz() As Long
            Return AbsoluteTimeToTicks27Mhz(Me.absoluteTime)
        End Function

        ''' <summary>
        ''' Returns the value of this instance in MPEG 2 PCR time base (PcrTb) format.
        ''' </summary>
        ''' <returns>A long value that is in PcrTb.</returns>
        Public Function ToTicksPcrTb() As Long
            Return AbsoluteTimeToTicksPcrTb(Me.absoluteTime)
        End Function

#End Region

		#Region "Private Static Methdos"

		''' <summary>
		''' Converts a SMPTE timecode to absolute time.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <param name="rate">The <see cref="SmpteFrameRate"/> of the timecode.</param>
		''' <returns>A <see cref="double"/> with the absolute time.</returns>
		Private Shared Function Smpte12mToAbsoluteTime(ByVal timeCode As String, ByVal rate As SmpteFrameRate) As Double
			Dim absoluteTime As Double = 0

			Select Case rate
				Case SmpteFrameRate.Smpte2398
					absoluteTime = Smpte12M_23_98_ToAbsoluteTime(timeCode)
				Case SmpteFrameRate.Smpte24
					absoluteTime = Smpte12M_24_ToAbsoluteTime(timeCode)
				Case SmpteFrameRate.Smpte25
					absoluteTime = Smpte12M_25_ToAbsoluteTime(timeCode)
				Case SmpteFrameRate.Smpte2997Drop
					absoluteTime = Smpte12M_29_97_Drop_ToAbsoluteTime(timeCode)
				Case SmpteFrameRate.Smpte2997NonDrop
					absoluteTime = Smpte12M_29_97_NonDrop_ToAbsoluteTime(timeCode)
				Case SmpteFrameRate.Smpte30
					absoluteTime = Smpte12M_30_ToAbsoluteTime(timeCode)
			End Select

			Return absoluteTime
		End Function

		''' <summary>
		''' Parses a timecode string for the different parts of the timecode.
		''' </summary>
		''' <param name="timeCode">The source timecode to parse.</param>
		''' <param name="hours">The Hours section from the timecode.</param>
		''' <param name="minutes">The Minutes section from the timecode.</param>
		''' <param name="seconds">The Seconds section from the timecode.</param>
		''' <param name="frames">The frames section from the timecode.</param>
		Private Shared Sub ParseTimecodeString(ByVal timeCode As String, <System.Runtime.InteropServices.Out()> ByRef hours As Integer, <System.Runtime.InteropServices.Out()> ByRef minutes As Integer, <System.Runtime.InteropServices.Out()> ByRef seconds As Integer, <System.Runtime.InteropServices.Out()> ByRef frames As Integer)
			If Not validateTimecode.IsMatch(timeCode) Then
				Throw New FormatException(Resources.Smpte12MBadFormat)
			End If

			Dim times() As String = timeCode.Split(":"c, ";"c)

			hours = Int32.Parse(times(0), CultureInfo.InvariantCulture)
			minutes = Int32.Parse(times(1), CultureInfo.InvariantCulture)
			seconds = Int32.Parse(times(2), CultureInfo.InvariantCulture)
			frames = Int32.Parse(times(3), CultureInfo.InvariantCulture)

			If (hours >= 24) OrElse (minutes >= 60) OrElse (seconds >= 60) OrElse (frames >= 30) Then
				Throw New FormatException(Resources.Smpte12MOutOfRange)
			End If
		End Sub

		''' <summary>
		''' Generates a string representation of the timecode.
		''' </summary>
		''' <param name="hours">The Hours section from the timecode.</param>
		''' <param name="minutes">The Minutes section from the timecode.</param>
		''' <param name="seconds">The Seconds section from the timecode.</param>
		''' <param name="frames">The frames section from the timecode.</param>
		''' <param name="dropFrame">Indicates whether the timecode is drop frame or not.</param>
		''' <returns>The timecode in string format.</returns>
		Private Shared Function FormatTimeCodeString(ByVal hours As Integer, ByVal minutes As Integer, ByVal seconds As Integer, ByVal frames As Integer, ByVal dropFrame As Boolean) As String
			Dim framesSeparator As String = ":"
			If dropFrame Then
				framesSeparator = ";"
			End If
			Return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2:D2}", FormatTimeCodeStringNoFrames(hours, minutes, seconds), framesSeparator, frames)
		End Function

		''' <summary>
		''' Generates a string representation of the timecode.
		''' </summary>
		''' <param name="hours">The Hours section from the timecode.</param>
		''' <param name="minutes">The Minutes section from the timecode.</param>
		''' <param name="seconds">The Seconds section from the timecode.</param>
		''' <returns>The timecode in string format.</returns>
		Private Shared Function FormatTimeCodeStringNoFrames(ByVal hours As Integer, ByVal minutes As Integer, ByVal seconds As Integer) As String
			Return New TimeSpan(hours, minutes, seconds).ToString()
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 23.98.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_23_98_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 24 Then
				Throw New FormatException(Resources.Smpte12M_2398_BadFormat)
			End If

			Return (1001 / 24000R) * (frames + (24 * seconds) + (1440 * minutes) + (86400 * hours))
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 24.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_24_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 24 Then
				Throw New FormatException(Resources.Smpte12M_24_BadFormat)
			End If

			Return (1 / 24R) * (frames + (24 * seconds) + (1440 * minutes) + (86400 * hours))
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 25.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_25_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 25 Then
				Throw New FormatException(Resources.Smpte12M_25_BadFormat)
			End If

			Return (1 / 25R) * (frames + (25 * seconds) + (1500 * minutes) + (90000 * hours))
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 29.97 Drop frame.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_29_97_Drop_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 30 Then
				Throw New FormatException(Resources.Smpte12M_2997_Drop_BadFormat)
			End If

			Return (1001 / 30000R) * (frames + (30 * seconds) + (1798 * minutes) + ((2 * (minutes \ 10)) + (107892 * hours)))
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 29.97 Non Drop.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_29_97_NonDrop_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 30 Then
				Throw New FormatException(Resources.Smpte12M_2997_NonDrop_BadFormat)
			End If

			Return (1001 / 30000R) * (frames + (30 * seconds) + (1800 * minutes) + (108000 * hours))
		End Function

		''' <summary>
		''' Converts to Absolute time from SMPTE 12M 30.
		''' </summary>
		''' <param name="timeCode">The timecode to parse.</param>
		''' <returns>A <see cref="double"/> that contains the absolute duration.</returns>
		Private Shared Function Smpte12M_30_ToAbsoluteTime(ByVal timeCode As String) As Double
			Dim hours, minutes, seconds, frames As Integer

			ParseTimecodeString(timeCode, hours, minutes, seconds, frames)

			If frames >= 30 Then
				Throw New FormatException(Resources.Smpte12M_30_BadFormat)
			End If

			Return (1 / 30R) * (frames + (30 * seconds) + (1800 * minutes) + (108000 * hours))
		End Function

		''' <summary>
		''' Converts from 27Mhz ticks to PCRTb.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A <see cref="long"/> with the PCRTb.</returns>
		Private Shared Function Ticks27MhzToPcrTb(ByVal ticks27Mhz As Long) As Long
			Return ticks27Mhz \ 300
		End Function

		''' <summary>
		'''     Converts the provided absolute time to PCRTb.
		''' </summary>
		''' <param name="absoluteTime">Absolute time to be converted.</param>
		''' <returns>The number of PCRTb ticks.</returns>
		Private Shared Function AbsoluteTimeToTicksPcrTb(ByVal absoluteTime As Double) As Long
			Return CLng(Fix((absoluteTime * 90000) Mod Math.Pow(2, 33)))
		End Function

		''' <summary>
		'''     Converts the specified absolute time to 27 mhz ticks.
		''' </summary>
		''' <param name="absoluteTime">Absolute time to be converted.</param>
		''' <returns>THe number of 27Mhz ticks.</returns>
		Private Shared Function AbsoluteTimeToTicks27Mhz(ByVal absoluteTime As Double) As Long
			Return AbsoluteTimeToTicksPcrTb(absoluteTime) * 300
		End Function

		''' <summary>
		'''     Converts the specified absolute time to absolute time.
		''' </summary>
		''' <param name="ticksPcrTb">Ticks PCRTb to be converted.</param>
		''' <returns>The absolute time.</returns>
		Private Shared Function TicksPcrTbToAbsoluteTime(ByVal ticksPcrTb As Long) As Double
			Return ticksPcrTb / 90000R
		End Function

		''' <summary>
		'''     Converts the specified absolute time to absolute time.
		''' </summary>
		''' <param name="ticks27Mhz">Ticks 27Mhz to be converted.</param>
		''' <returns>The absolute time.</returns>
		Private Shared Function Ticks27MhzToAbsoluteTime(ByVal ticks27Mhz As Long) As Double
			Dim ticksPcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Return TicksPcrTbToAbsoluteTime(ticksPcrTb)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <param name="rate">The SMPTE frame rate.</param>
		''' <returns>A string in SMPTE 12M format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M(ByVal absoluteTime As Double, ByVal rate As SmpteFrameRate) As String
			Dim timeCode As String = String.Empty

			Select Case rate
				Case SmpteFrameRate.Smpte2398
					timeCode = AbsoluteTimeToSmpte12M_23_98fps(absoluteTime)
				Case SmpteFrameRate.Smpte24
					timeCode = AbsoluteTimeToSmpte12M_24fps(absoluteTime)
				Case SmpteFrameRate.Smpte25
					timeCode = AbsoluteTimeToSmpte12M_25fps(absoluteTime)
				Case SmpteFrameRate.Smpte2997Drop
					timeCode = AbsoluteTimeToSmpte12M_29_97_Drop(absoluteTime)
				Case SmpteFrameRate.Smpte2997NonDrop
					timeCode = AbsoluteTimeToSmpte12M_29_97_NonDrop(absoluteTime)
				Case SmpteFrameRate.Smpte30
					timeCode = AbsoluteTimeToSmpte12M_30fps(absoluteTime)
				Case SmpteFrameRate.Unknown
					timeCode = AbsoluteTimeToHMS(absoluteTime)
			End Select

			Return timeCode
		End Function

		''' <summary>
		''' Returns the number of frames.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to use for parsing from.</param>
		''' <param name="rate">The SMPTE frame rate to use for the conversion.</param>
		''' <returns>A <see cref="long"/> with the number of frames.</returns>
		Private Shared Function AbsoluteTimeToFrames(ByVal absoluteTime As Double, ByVal rate As SmpteFrameRate) As Long
			Select Case rate
				Case SmpteFrameRate.Smpte2398
					Return CLng(Fix(Math.Floor(24 * (1000 / 1001R) * absoluteTime)))
				Case SmpteFrameRate.Smpte24
					Return Convert.ToInt64(24 * absoluteTime)
				Case SmpteFrameRate.Smpte25
					Return Convert.ToInt64(25 * absoluteTime)
				Case SmpteFrameRate.Smpte2997Drop, SmpteFrameRate.Smpte2997NonDrop
					Return CLng(Fix(Math.Floor(30 * (1000 / 1001R) * absoluteTime)))
				Case SmpteFrameRate.Smpte30
					Return Convert.ToInt64(30 * absoluteTime)
				Case Else
					Return Convert.ToInt64(30 * absoluteTime)
			End Select
		End Function

		''' <summary>
		''' Returns the absolute time.
		''' </summary>
		''' <param name="frames">The number of frames.</param>
		''' <param name="rate">The SMPTE frame rate to use for the conversion.</param>
		''' <returns>The absolute time.</returns>
		Private Shared Function FramesToAbsoluteTime(ByVal frames As Long, ByVal rate As SmpteFrameRate) As Double
			Select Case rate
				Case SmpteFrameRate.Smpte2398
					Return Math.Ceiling(frames / 24R / (1000 / 1001R))
				Case SmpteFrameRate.Smpte24
					Return Math.Ceiling(frames / 24R)
				Case SmpteFrameRate.Smpte25
					Return Math.Ceiling(frames / 25R)
				Case SmpteFrameRate.Smpte2997Drop, SmpteFrameRate.Smpte2997NonDrop
					Return frames / 30R / (1000 / 1001R)
				Case SmpteFrameRate.Smpte30
					Return frames / 30R
				Case Else
					Return frames / 30R
			End Select
		End Function

		''' <summary>
		''' Returns the SMPTE 12M 23.98 timecode.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_23_98fps(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte2398)
			Dim hours As Integer = Convert.ToInt32((framecount \ 86400) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (86400 * hours)) / 1440)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1440 * minutes) - (86400 * hours)) / 24)
			Dim frames As Integer = Convert.ToInt32(framecount - (24 * seconds) - (1440 * minutes) - (86400 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 24fps.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_24fps(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte24)
			Dim hours As Integer = Convert.ToInt32((framecount \ 86400) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (86400 * hours)) / 1440)
			Dim seconds As Integer = Convert.ToInt32(((framecount - (1440 * minutes) - (86400 * hours)) / 24))
			Dim frames As Integer = Convert.ToInt32(framecount - (24 * seconds) - (1440 * minutes) - (86400 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 25fps.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_25fps(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte25)
			Dim hours As Integer = Convert.ToInt32((framecount \ 90000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (90000 * hours)) / 1500)
			Dim seconds As Integer = Convert.ToInt32(((framecount - (1500 * minutes) - (90000 * hours)) / 25))
			Dim frames As Integer = Convert.ToInt32(framecount - (25 * seconds) - (1500 * minutes) - (90000 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 29.97fps Drop.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_29_97_Drop(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte2997Drop)
			Dim hours As Integer = CInt(Fix((framecount \ 107892) Mod 24))
			Dim minutes As Integer = Convert.ToInt32((framecount + (2 * (CInt(Fix((framecount - (107892 * hours)) / 1800)))) - (2 * (CInt(Fix((framecount - (107892 * hours)) / 18000)))) - (107892 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1798 * minutes) - (2 * (CInt(Fix(minutes / 10R)))) - (107892 * hours)) / 30)
			Dim frames As Integer = Convert.ToInt32(framecount - (30 * seconds) - (1798 * minutes) - (2 * (CInt(Fix(minutes / 10R)))) - (107892 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, True)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 29.97fps Non Drop.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_29_97_NonDrop(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte2997NonDrop)
			Dim hours As Integer = Convert.ToInt32((framecount \ 108000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (108000 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32(((framecount - (1800 * minutes) - (108000 * hours)) / 30))
			Dim frames As Integer = Convert.ToInt32(framecount - (30 * seconds) - (1800 * minutes) - (108000 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 30fps.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToSmpte12M_30fps(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Smpte30)
			Dim hours As Integer = Convert.ToInt32((framecount \ 108000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (108000 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32(((framecount - (1800 * minutes) - (108000 * hours)) / 30))
			Dim frames As Integer = Convert.ToInt32(framecount - (30 * seconds) - (1800 * minutes) - (108000 * hours))

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts absolute time to HMS.
		''' </summary>
		''' <param name="absoluteTime">The absolute time to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function AbsoluteTimeToHMS(ByVal absoluteTime As Double) As String
			Dim framecount As Long = AbsoluteTimeToFrames(absoluteTime, SmpteFrameRate.Unknown)
			Dim hours As Integer = Convert.ToInt32((framecount \ 108000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (108000 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32(((framecount - (1800 * minutes) - (108000 * hours)) / 30))

			Return FormatTimeCodeStringNoFrames(hours, minutes, seconds)
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_30fpsToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte30)
			Dim ticksPcrTb As Long = (t.FramesSegment * 3000) + (90000 * t.SecondsSegment) + (5400000 * t.MinutesSegment) + (324000000 * t.HoursSegment)
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_23_98fpsToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte2398)
			Dim ticksPcrTb As Long = Convert.ToInt64((Math.Ceiling(1001 * (15 / 4R) * t.FramesSegment) + (90090 * t.SecondsSegment) + (5405400 * t.MinutesSegment) + (324324000R * t.HoursSegment)))
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_24fpsToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte24)
			Dim ticksPcrTb As Long = (t.FramesSegment * 3750) + (90000 * t.SecondsSegment) + (5400000 * t.MinutesSegment) + (324000000 * t.HoursSegment)
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_25fpsToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte25)
			Dim ticksPcrTb As Long = (t.FramesSegment * 3600) + (90000 * t.SecondsSegment) + (5400000 * t.MinutesSegment) + (324000000 * t.HoursSegment)
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_29_27_NonDropToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte2997Drop)
			Dim ticksPcrTb As Long = (t.FramesSegment * 3003) + (90090 * t.SecondsSegment) + (5405400 * t.MinutesSegment) + (324324000 * t.HoursSegment)
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to Ticks 27Mhz.
		''' </summary>
		''' <param name="timeCode">The timecode to convert from.</param>
		''' <returns>The number of 27Mhz ticks.</returns>
		Private Shared Function Smpte12M_29_27_DropToTicks27Mhz(ByVal timeCode As String) As Long
			Dim t As New TimeCode(timeCode, SmpteFrameRate.Smpte2997NonDrop)
			Dim ticksPcrTb As Long = (3003 * t.FramesSegment) + (90090 * t.SecondsSegment) + (5399394 * t.MinutesSegment) + (6006 * CInt(Fix(t.MinutesSegment / 10R))) + (323999676 * t.HoursSegment)
			Return ticksPcrTb * 300
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 29.27fps Non Drop.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_29_27_NonDrop(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = CInt(Fix(pcrTb \ 3003))
			Dim hours As Integer = Convert.ToInt32((framecount \ 108000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (108000 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1800 * minutes) - (108000 * hours)) / 30)
			Dim frames As Integer = framecount - (30 * seconds) - (1800 * minutes) - (108000 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 29.27fps Non Drop.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_29_27_Drop(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = Convert.ToInt32(pcrTb \ 3003)
			Dim hours As Integer = Convert.ToInt32((framecount \ 107892) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount + (2 * Convert.ToInt32((framecount - (107892 * hours)) / 1800)) - (2 * Convert.ToInt32((framecount - (107892 * hours)) / 18000)) - (107892 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1798 * minutes) - (2 * Convert.ToInt32(minutes \ 10)) - (107892 * hours)) / 30)
			Dim frames As Integer = framecount - (30 * seconds) - (1798 * minutes) - (2 * Convert.ToInt32(minutes \ 10)) - (107892 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, True)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 23.98fps.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_23_98fps(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = CInt(Fix((4 / 15R) * (pcrTb / 1001R)))
			Dim hours As Integer = Convert.ToInt32((framecount \ 86400) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (86400 * hours)) / 1440)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1440 * minutes) - (86400 * hours)) / 24)
			Dim frames As Integer = framecount - (24 * seconds) - (1440 * minutes) - (86400 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 24fps.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_24fps(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = CInt(Fix(pcrTb \ 3750))
			Dim hours As Integer = Convert.ToInt32((framecount \ 86400) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (86400 * hours)) / 1440)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1440 * minutes) - (86400 * hours)) / 24)
			Dim frames As Integer = framecount - (24 * seconds) - (1440 * minutes) - (86400 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 25fps.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_25fps(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = CInt(Fix(pcrTb \ 3600))
			Dim hours As Integer = Convert.ToInt32((framecount \ 90000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (90000 * hours)) / 1500)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1500 * minutes) - (90000 * hours)) / 25)
			Dim frames As Integer = framecount - (25 * seconds) - (1500 * minutes) - (90000 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		''' <summary>
		''' Converts to SMPTE 12M 30fps.
		''' </summary>
		''' <param name="ticks27Mhz">The number of 27Mhz ticks to convert from.</param>
		''' <returns>A string that contains the correct format.</returns>
		Private Shared Function Ticks27MhzToSmpte12M_30fps(ByVal ticks27Mhz As Long) As String
			Dim pcrTb As Long = Ticks27MhzToPcrTb(ticks27Mhz)
			Dim framecount As Integer = CInt(Fix(pcrTb \ 3000))
			Dim hours As Integer = Convert.ToInt32((framecount \ 108000) Mod 24)
			Dim minutes As Integer = Convert.ToInt32((framecount - (108000 * hours)) / 1800)
			Dim seconds As Integer = Convert.ToInt32((framecount - (1800 * minutes) - (108000 * hours)) / 30)
			Dim frames As Integer = framecount - (30 * seconds) - (1800 * minutes) - (108000 * hours)

			Return FormatTimeCodeString(hours, minutes, seconds, frames, False)
		End Function

		#Region "Unused Code"

'        
'
'        /// <summary>
'        ///     Converts the specified absolute time to PCRtb
'        /// </summary>
'        /// <param name="ticksPcrTb">PCR-tb time to be converted</param>
'        private static double PcrTbToAbsoluteTime(long ticksPcrTb)
'        {
'            double absoluteTime = ticksPcrTb / 90000;
'            return absoluteTime;
'        }
'        

		#End Region

		#End Region
	End Structure


	''' <summary>
	''' extension to allow screen reader to read times out nicely.
	''' </summary>
	Public Module TimeSpanExtensions
		<System.Runtime.CompilerServices.Extension> _
		Public Function ReadableTime(ByVal ts As TimeSpan) As String
			Dim sb As New StringBuilder()

			If ts.Hours = 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerHour)
			ElseIf ts.TotalHours > 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerHours, Convert.ToInt32(ts.TotalHours))
			End If

			If ts.Minutes > 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerMinutes, Convert.ToInt32(ts.Minutes))
			ElseIf ts.Minutes = 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerMinute)
			End If

			If ts.Seconds > 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerSeconds, Convert.ToInt32(ts.Seconds))
			ElseIf ts.Seconds = 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerSecond)
			ElseIf ts.TotalSeconds < 1.0 Then
				sb.AppendFormat(CultureInfo.InvariantCulture, Resources.readerSeconds, ts.TotalSeconds)
			End If

			Return sb.ToString()

		End Function
	End Module
End Namespace